<template>
  <div>
    <div v-if="!readonly" style="text-align: right">
      <el-button-group style="margin-right: 12px; margin-bottom: 4px;">
        <el-tooltip :open-delay="500" content="发送" placement="top" style="margin-right: 6px;">
          <el-button icon="el-icon-position" style="height: 29px;" size="mini" type="success" @click="handleSend">发送</el-button>
        </el-tooltip>
        <el-tooltip :open-delay="500" content="历史记录" placement="top">
          <el-button :disabled="element.id===undefined" icon="el-icon-link" style="height: 29px;" size="mini" type="info" @click="handleHistory" />
        </el-tooltip>
        <el-tooltip :open-delay="500" content="下一个" placement="top">
          <el-button icon="el-icon-right" style="height: 29px;" size="mini" type="primary" @click="next" />
        </el-tooltip>
        <el-tooltip :open-delay="500" content="保存并继续" placement="top">
          <el-button icon-class="skill" size="mini" type="primary" @click="saveTestCase(true)"><svg-icon icon-class="save-continuer" /></el-button>
        </el-tooltip>
        <el-tooltip content="保存" placement="top" :open-delay="500">
          <el-button type="primary" icon-class="skill" size="mini" @click="saveTestCase(false)"><svg-icon icon-class="save" /></el-button>
        </el-tooltip>
      </el-button-group>
    </div>
    <el-card shadow="hover" style="height: 75vh; overflow-y: scroll;">
      <el-form ref="element" size="small" class="demo-table-expand" :rules="rules" label-width="100px" label-position="right" :model="element" :disabled="readonly">
        <el-row :gutter="5">
          <el-form-item label="用例名称" prop="name">
            <el-col :span="17">
              <el-input v-model="element.name" placeholder="请输入用例名称" />
            </el-col>
            <el-col :span="4">
              <el-tooltip :open-delay="500" content="用例编写状态" placement="top">
                <el-select v-model="element.editStatus" placeholder="请选择用例编写状态" style="width: 100%">
                  <el-option v-for="item in editStatusOptions" :key="item.value" :label="item.name" :value="item.value" />
                </el-select>
              </el-tooltip>
            </el-col>
            <el-col :span="3">
              <el-tooltip :open-delay="500" content="优先级" placement="top">
                <el-select v-model="element.priority" placeholder="请选择优先级" style="width: 100%">
                  <el-option v-for="item in priorityOptions" :key="item.value" :label="item.name" :value="item.value" />
                </el-select>
              </el-tooltip>
            </el-col>
          </el-form-item>
        </el-row>
        <div style="text-align: right; margin-bottom: 4px;">
          <el-tooltip :open-delay="500" content="DataProvider" placement="top">
            <el-button icon="el-icon-s-grid" size="mini" type="primary" @click="handlerDataProvider()" />
          </el-tooltip>
        </div>
        <step-list-component :api-options="stepOptions" :steps="element.steps" @setParent="setParent" @addStep="addStep" @sortStep="sortStep" @editStep="editStep" @copyStep="copyStep" @deleteStep="deleteStep" />
      </el-form>
      <div style="text-align: center; margin-top: 20px;">
        <el-button size="mini" type="primary" @click="addStep(0)">新增步骤</el-button>
      </div>
    </el-card>

    <!-- 操作步骤 -->
    <el-dialog v-model="dialogVisible" :visible.sync="dialogVisible" title="操作步骤" width="800px" :close-on-click-modal="false">
      <step-edit-component
        v-if="dialogVisible"
        :step-index="tmp.stepIndex"
        :step="tmp.step"
        :step-options="stepOptions"
        @flush="flushStep"
      />
    </el-dialog>

    <!-- DataProvider -->
    <el-dialog :visible.sync="dataProviderDialogVisible" title="DataProivder（数据驱动）" width="1280px" :close-on-click-modal="false">
      <el-alert close-text="Get!" style="margin-bottom: 10px" title="测试用例 数据驱动 & 参数化：" type="info">
        <div>1、使我们能够通过传递不同的数据集，多次运行这个测试用例；</div>
        <div>2、第一行数据为参数名，其余各行是参数值；</div>
        <div>3、有多少行参数值，就会重复执行多少次测试用例。每次执行用例会将一行数据当参数传入测试用例。</div>
      </el-alert>
      <div align="right" style="margin-bottom: 4px">
        <el-button icon-class="skill" size="mini" style="height:28px;" type="primary" @click="handlerImport"><i class="el-icon-upload2" />导入数据</el-button>
        <el-button icon-class="skill" size="mini" style="height:28px;" type="primary" @click="handlerInsertCol"><svg-icon icon-class="add-column" />插入列</el-button>
      </div>
      <el-table :key="dataProviderTableKey" ref="dataProviderTable" :data="dataProviderTableData" border height="400px" size="mini" style="width: 100%;">
        <el-table-column v-if="!readonly" align="center" label="操作" width="80">
          <template v-slot="scope">
            <el-tooltip v-if="scope.$index !== dataProviderTableData.length - 1" :open-delay="500" :tabindex="-1" content="删除" placement="top">
              <span class="icon-wrapper"><i class="el-icon-close icon-style" @click="delTableRowDataProviderTableData(scope.$index)" /></span>
            </el-tooltip>
          </template>
        </el-table-column>
        <el-table-column v-for="fruit in dataProviderTableTheads" :key="fruit" :label="fruit" align="center" width="150">
          <template slot-scope="scope">
            <el-input v-if="scope.$index !== dataProviderTableData.length - 1" v-model="scope.row[fruit]" autosize class="item" type="textarea" @input="changeDataProviderTableRow()" />
            <el-input v-if="scope.$index === dataProviderTableData.length - 1" v-model="scope.row[fruit]" autosize class="item" type="textarea" @input="newDataProviderTableRow()" />
          </template>
        </el-table-column>
      </el-table>
    </el-dialog>

    <!-- 导入数据 -->
    <el-dialog :visible.sync="importDialogVisible" title="导入数据" width="500px" :close-on-click-modal="false">
      <upload-excel-component :on-success="importSuccess" :before-upload="importBeforeUpload" />
    </el-dialog>

    <el-dialog :visible.sync="historyDialogFormVisible" title="历史记录" width="1200px" :close-on-click-modal="false">
      <test-case-history-component
        ref="testCaseHistoryComponent"
        @dblclickResultRow="dblclickResultRow"
      />
    </el-dialog>
  </div>
</template>
<style lang="scss">
.item {
  .el-input__inner {
    border: 1px solid #fff !important;
    resize: none;
  }
  .el-textarea__inner {
    border: 1px solid #fff !important;
    resize: none;
  }
}
</style>
<style>
.icon-style {
  font-size: 15px;
}
.icon-wrapper {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  color: #606266;
  width: 20px;
  height: 20px;
}
.icon-wrapper:hover {
  color: #409EFF;
}
</style>
<script>
import { deepClone } from '@/utils'
import { create as testCaseCreate, testCaseDebug, update as testCaseUpdate } from '@/api/functional_test/test_case_manage/test_case'
import { deleteByStepId } from '@/api/functional_test/test_case_manage/test_step'
import { listTree } from '@/api/functional_test/catalog'
import StepListComponent from '@/components/functional_test/TestCase/StepList.vue'
import StepEditComponent from '@/components/functional_test/TestCase/StepEdit.vue'
import TestCaseHistoryComponent from '@/components/functional_test/History/testCaseHistory'
import { editStatusOptions } from '@/enum/editStatus.js'
import { priorityOptions } from '@/enum/priority.js'
import UploadExcelComponent from '@/components/UploadExcel/index.vue'

export default {
  components: { StepListComponent, StepEditComponent, TestCaseHistoryComponent, UploadExcelComponent },
  props: {
    value: {
      type: Object,
      default() {
        return {
          catalogId: 0 // 分类 id
        }
      }
    },
    readonly: { // 是否只读
      type: Boolean,
      default: false
    },
    debugEnv: { // 调试使用的环境
      type: String,
      default: ''
    },
    debugAgentId: { // 调试使用的环境
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      editStatusOptions, // 编写状态 options
      priorityOptions, // 优先级 options
      activeName: 'Body',
      isChange: false, // 是否有改动
      dialogVisible: false, // 步骤信息 dialog
      dataProviderDialogVisible: false, // DataProvider dialog
      importDialogVisible: false, // 导入数据 对话框
      dataProviderTableKey: Date.now(),
      dataProviderTableTheads: [],
      dataProviderTableData: [],
      cascaderProps: {
        value: 'id',
        label: 'name',
        children: 'children',
        leaf: 'isCatalog'
      },
      tmp: {
        stepIndex: undefined,
        step: undefined
      },
      tempId: -1, // 临时 stepId（因为是一次提交所有 steps，这样如果是新增的 step，并在这个 step 下又有 step，就需要一个临时 id，如果没有无法通过 parentId 遍历查找到）
      parentId: 0,
      element: {}, // element 对象
      caseType: null, // 用例类型
      stepOptions: [
        {
          id: -11,
          name: 'HTTP',
          key: 't-11',
          parentId: 0,
          isCatalog: true,
          children: []
        },
        {
          id: -12,
          name: 'Websocket',
          key: 't-12',
          parentId: 0,
          isCatalog: true,
          children: []
        },
        // {
        //   id: -13,
        //   name: 'DataChannel',
        //   key: 't-13',
        //   parentId: 0,
        //   isCatalog: true,
        //   children: []
        // },
        {
          id: -14,
          name: 'Database',
          key: 't-14',
          parentId: 0,
          isCatalog: true,
          children: []
        },
        {
          id: -15,
          name: 'SSH',
          key: 't-15',
          parentId: 0,
          isCatalog: true,
          children: []
        },
        {
          id: -4,
          name: '公共步骤',
          key: 't-4',
          parentId: 0,
          isCatalog: true,
          children: []
        },
        {
          id: -103,
          name: '断言',
          key: 'e--103',
          parentId: 0,
          isCatalog: false
        },
        {
          id: -104,
          name: '保存变量',
          key: 'e--104',
          parentId: 0,
          isCatalog: false
        },
        {
          id: -108,
          name: '设置变量',
          key: 'e--108',
          parentId: 0,
          isCatalog: false
        },
        {
          id: -105,
          name: '强制等待',
          key: 'e--105',
          parentId: 0,
          isCatalog: false
        },
        {
          id: -106,
          name: 'WebSocket 智能等待',
          key: 'e--106',
          parentId: 0,
          isCatalog: false
        },
        {
          id: -101,
          name: '步骤间隔',
          key: 'e--101',
          parentId: 0,
          isCatalog: false
        },
        {
          id: -107,
          name: '自定义脚本',
          key: 'e--107',
          parentId: 0,
          isCatalog: false
        }
      ],
      historyDialogFormVisible: false, // 历史记录弹窗
      rules: {
        name: [
          { required: true, message: '测试用例名称不能为空', trigger: 'blur' },
          { max: 255, message: '测试用例名称长度不能超过 255 个字符', trigger: 'blur' }
        ]
      }
    }
  },
  watch: {
    caseType: {
      handler: function(caseType) {
        if (caseType === 4) { // 公共步骤 中 去掉公共步骤（不允许嵌套添加公共步骤）
          this.stepOptions = this.stepOptions.filter(item => {
            if (item.id !== -4) {
              return item
            }
          })
        }

        this.initStepData()
      }
    }
  },
  created() {
  },
  mounted() {
  },
  methods: {
    resetElement() {
      this.element = {
        projectId: this.$store.getters.projectId,
        catalogId: this.value.catalogId,
        caseType: this.caseType,
        name: '',
        before: '',
        editStatus: 1,
        priority: 2,
        steps: []
      }
      this.dataProviderTableTheads = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P']
      this.dataProviderTableData = [{}]
    },
    // 循环加载步骤类型数据
    getListTypeData() {
      for (const tmp of this.stepOptions) {
        if (Math.abs(tmp.id) > 100) {
          break
        }
        listTree({
          projectId: this.$store.getters.projectId,
          type: Math.abs(tmp.id),
          parentId: 0
        }).then(response => {
          tmp.children = response.data
        })
      }
    },
    initStepData() {
      this.getListTypeData()
    },
    // 设置 element 数据（当服务端返回数据后有些类型不符需要在这个方法中转换）
    setElement(ele, caseType) {
      if (this.isChange) {
        this.$alert('当前用例有修改，请先保存！', '提示', {
          confirmButtonText: '确定',
          type: 'warning'
        }).then(() => {
        })
        return
      }
      this.caseType = caseType
      this.element = this.setTableRowKey(ele)
      this.isChange = false
      this.dataProviderTableData = JSON.parse(ele.dataProvider) || [{}]

      // 设置表头
      let maxCol = 0 // 最大列名的 10 进制数
      for (const objRow of this.dataProviderTableData) { // 遍历 dataProviderTableData 获取最大列名的 10 进制数
        for (const key in objRow) {
          if (maxCol < this.convertToDecimal(key)) {
            maxCol = this.convertToDecimal(key)
          }
        }
      }
      if (maxCol === 0) { // 如果没有列，则默认为 A-P
        this.dataProviderTableTheads = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P']
      } else { // 如果有列，则根据最大列名的 10 进制数生成表头
        this.dataProviderTableTheads = []
        for (let i = 1; i <= maxCol; i++) {
          this.dataProviderTableTheads.push(this.convertToLetter(i))
        }
      }
    },
    setTableRowKey(element) {
      let unique = new Date().getTime() - 10000
      element.steps.forEach((item, index) => {
        if (item.stepType === 12) { // WebSocket 接口，需要将数据库中的 json 字符串转成对象
          item.extra4 = JSON.parse(item.extra4)
        }
        item.tableRowKey = unique++
      })
      return element
    },
    // 点击 删除 操作
    deleteStep(index) {
      // 找到对应的 steps
      let tempSteps = []
      if (this.parentId === 0) { // 父节点 ID 为 0，则 steps 就是 tempSteps
        tempSteps = this.element.steps
      } else { // 父节点 ID 不为 0，则跟父节点 ID 相同的 step 下的 childSteps 是 tempSteps
        const tempChildSteps = this.recursionFindChildSteps(this.element.steps, this.parentId)
        if (tempChildSteps !== null) {
          tempSteps = tempChildSteps
        }
      }

      if (tempSteps[index].id > 0 && tempSteps[index].id !== undefined) {
        deleteByStepId(tempSteps[index].id).then(res => {
          this.$message({
            type: 'success',
            message: '删除成功!'
          })
          tempSteps.splice(index, 1)
        })
      } else {
        this.$message({
          type: 'success',
          message: '删除成功!'
        })
        tempSteps.splice(index, 1)
      }
    },
    // 点击新增用例按钮时调用此方法
    add(caseType) {
      this.caseType = caseType
      this.resetElement()
    },
    // 点击 编辑 操作
    editStep(index) {
      // 找到对应的 step
      let tempStep = {}
      if (this.parentId === 0) { // 父节点 ID 为 0，则 this.element.steps[index] 就是 tempStep
        tempStep = this.element.steps[index]
      } else { // 父节点 ID 不为 0，则跟父节点 ID 相同的 step 下的 childSteps[index] 是 tempStep
        const temp = this.recursionFindChildSteps(this.element.steps, this.parentId)
        if (temp !== null) {
          tempStep = temp[index]
        }
      }

      this.tmp.stepIndex = index
      this.tmp.step = deepClone(tempStep)
      this.dialogVisible = true
    },
    // 点击操作中的 复制
    copyStep(index) {
      // 找到对应的 step
      let tempStep = {}
      if (this.parentId === 0) { // 父节点 ID 为 0，则 this.element.steps[index] 就是 tempStep
        tempStep = this.element.steps[index]
      } else { // 父节点 ID 不为 0，则跟父节点 ID 相同的 step 下的 childSteps[index] 是 tempStep
        const temp = this.recursionFindChildSteps(this.element.steps, this.parentId)
        if (temp !== null) {
          tempStep = temp[index]
        }
      }

      this.tmp.stepIndex = index
      this.tmp.step = deepClone(tempStep)
      this.tmp.stepIndex = -1 // 复制后清除 index，保存后会 push 到 steps 中
      this.tmp.step.id = this.tempId-- // 复制后清除 步骤id
      this.tmp.step.desc = this.tmp.step.desc + ' - copy' // 复制后 步骤描述 增加 copy
      this.tmp.step.tableRowKey = new Date().getTime() // 复制后清除 key
      this.dialogVisible = true
    },
    // step 拖动后排序
    sortStep(e) {
      // 找到对应的 steps
      let tempSteps = []
      if (this.parentId === 0) { // 父节点 ID 为 0，则 steps 就是 tempSteps
        tempSteps = this.element.steps
      } else { // 父节点 ID 不为 0，则跟父节点 ID 相同的 step 下的 childSteps 是 tempSteps
        const tempChildSteps = this.recursionFindChildSteps(this.element.steps, this.parentId)
        if (tempChildSteps !== null) {
          tempSteps = tempChildSteps
        }
      }

      const currRow = tempSteps.splice(e.oldIndex, 1)[0]
      tempSteps.splice(e.newIndex, 0, currRow)
    },
    // 关闭 步骤信息 dialog 后回调此方法刷新步骤列表中的步骤信息
    flushStep(index, step) {
      this.isChange = true

      // 找到对应的 steps
      let tempSteps = []
      if (this.parentId === 0) { // 父节点 ID 为 0，则 steps 就是 tempSteps
        tempSteps = this.element.steps
      } else { // 父节点 ID 不为 0，则跟父节点 ID 相同的 step 下的 childSteps 是 tempSteps
        const tempChildSteps = this.recursionFindChildSteps(this.element.steps, this.parentId)
        if (tempChildSteps !== null) {
          tempSteps = tempChildSteps
        }
      }

      if (index === -1) {
        tempSteps.push(step)
      } else {
        this.$set(tempSteps, index, step)
      }
      this.dialogVisible = false
    },
    // 递归 在 steps 中查找 childSteps
    recursionFindChildSteps(stepList, parentId) {
      for (const step of stepList) {
        if (step.id === parentId) {
          return step.childSteps
        }
        if (step.childSteps) {
          const ret = this.recursionFindChildSteps(step.childSteps, parentId)
          if (ret !== null) {
            return ret
          }
        }
      }
      return null
    },
    // 点击 新增步骤
    addStep(parentId) {
      if (parentId === 0) {
        this.parentId = 0
      }

      this.tmp.stepIndex = -1
      this.tmp.step = {
        id: this.tempId--,
        projectId: this.$store.getters.projectId,
        // step: '',
        // isEdit: false,
        parentId: this.parentId,
        stepType: 0,
        conditionType: 0,
        errorHandlingType: 3,
        desc: '',
        apiId: 0,
        extra1: '',
        extra2: '',
        extra3: '',
        extra4: '',
        extra5: '',
        tableRowKey: new Date().getTime()
      }
      this.dialogVisible = true
    },
    /**
     * 保存 测试用例
     * @param saveAndContinue 保存继续时，要传入 tree
     */
    saveTestCase(saveAndContinue) {
      this.$refs['element'].validate((valid) => {
        if (valid) {
          this.isChange = false
          this.element.type = this.caseType // 分类 type，3 接口测试用例
          this.element.caseType = this.caseType // 用例 type: 1 手工用例 , 2 自动化用例

          // 删除空步骤
          const delIndexs = []
          this.element.steps.forEach((item, index) => {
            if (item.stepType === 12) { // WebSocket 接口，需要将 json 对象转成字符串保存到数据库
              if (typeof item.extra4 === 'object') {
                item.extra4 = JSON.stringify(item.extra4)
              }
            }
            if (item.stepType === 0 && item.apiId === 0 && item.desc === '') {
              delIndexs.push(index)
            }
          })
          for (let i = delIndexs.length - 1; i >= 0; i--) {
            this.element.steps.splice(delIndexs[i], 1)
          }

          // 删除 dataProvider 的中间空数据行
          this.dataProviderTableData = this.dataProviderTableData.filter(item => {
            let erasure = true
            for (const key in item) {
              if (item[key].length > 0) {
                erasure = false
                break
              }
            }
            return !erasure
          })
          this.dataProviderTableData.push({}) // 最后添加空行
          this.element.dataProvider = JSON.stringify(this.dataProviderTableData) // 设置 dataProvider

          if (!this.element.id || this.element.id === 0) { // id = 0，新增
            if (!this.element.catalogId) { // 跟目录设置 parentId = 0
              this.element.catalogId = 0
            }
            testCaseCreate(this.element).then((response) => {
              if (saveAndContinue) { // 保存并继续
                this.resetElement()
              } else { // 保存
                this.element = this.setTableRowKey(response.data)
              }
              this.$emit('create', response.data)
              this.$message({ message: '添加成功!', type: 'success' })
            })
          } else { // 否则修改
            testCaseUpdate(this.element).then((response) => {
              if (saveAndContinue) { // 保存并继续
                this.$emit('updateNext', response.data)
              } else { // 保存
                this.element = this.setTableRowKey(response.data)
                this.$emit('update', response.data)
              }
              this.$message({ message: '修改成功!', type: 'success' })
            })
          }
        }
      })
    },
    // 查找 接口（api）名称
    findApiName(row, id) {
      for (const item of this.stepOptions) {
        if ((-row.stepType) === item.id) {
          if (row.stepType <= 100) { // type 小于 100，表示是接口
            if (item.children) {
              const ret = this.recursionFindApiName(item.children, id)
              if (ret) {
                return item.name + ' / ' + ret
              }
            }
          } else {
            return item.name
          }
        }
      }
    },
    // 递归查找 接口（api）名称
    recursionFindApiName(apiList, id) {
      for (const api of apiList) {
        if (!api.isCatalog && api.id === id) {
          return api.name
        }
        if (api.children) {
          const ret = this.recursionFindApiName(api.children, id)
          if (ret) {
            return api.name + ' / ' + ret
          }
        }
      }
    },
    // 下一个 tree 节点
    next() {
      this.$emit('next')
    },
    // 点击 DataProvider 按钮
    handlerDataProvider() {
      this.dataProviderDialogVisible = true
    },
    // 修改 DataProviderTable 数据
    changeDataProviderTableRow() {
      this.isChange = true
    },
    // 滚动 DataProviderTable 到最下后新增一行表格数据
    newDataProviderTableRow() {
      this.isChange = true
      this.dataProviderTableData.push({})
      this.$nextTick(() => { // table 超出后滚动到最后
        this.$refs.dataProviderTable.bodyWrapper.scrollTop = this.$refs.dataProviderTable.bodyWrapper.scrollHeight
      })
    },
    // 单击 插入列
    handlerInsertCol() {
      if (this.dataProviderTableTheads.length > 0) {
        this.dataProviderTableTheads.push(this.nextColumnName(this.dataProviderTableTheads[this.dataProviderTableTheads.length - 1]))
      } else {
        this.dataProviderTableTheads.push('A')
      }
    },
    // 单击 导入数据
    handlerImport() {
      this.importDialogVisible = true
    },
    // 导入 excel 文件前，回调
    importBeforeUpload(file) {
      const isLt1M = file.size / 1024 / 1024 < 1
      if (isLt1M) {
        return true
      }

      this.$message({
        message: '请不要上传大于 1M 的文件',
        type: 'warning'
      })
      return false
    },
    // 导入 excel 成功，回调
    importSuccess({ results, header }) {
      this.dataProviderTableTheads = []
      this.dataProviderTableData = []

      const headerMappingColumnName = {} // 表头对应列名（临时变量）
      let columnName = '' // 列名
      const row = {} // 行数据

      // 遍历导入 excel 的 header
      for (const head of header) {
        if (this.dataProviderTableTheads.length === 0) {
          columnName = 'A'
        } else {
          columnName = this.nextColumnName(this.dataProviderTableTheads[this.dataProviderTableTheads.length - 1])
        }
        this.dataProviderTableTheads.push(columnName)
        headerMappingColumnName[head] = columnName
        row[columnName] = head
      }
      this.dataProviderTableData.push(row) // 将 header 当第一行，插入到 dataProviderTableData

      // 遍历导入 excel 的 results
      for (const result of results) {
        const row = {}
        for (const key in result) {
          row[headerMappingColumnName[key]] = result[key]
        }
        this.dataProviderTableData.push(row)
      }
      this.dataProviderTableData.push({}) // 在最后补一个空行

      this.importDialogVisible = false
    },
    // 得到下一个列名
    nextColumnName(columnName) {
      const columnNameDecimal = this.convertToDecimal(columnName)
      return this.convertToLetter(columnNameDecimal + 1)
    },
    // 26 进制转 10 进制。例如：AA -> 27
    convertToDecimal(columnName) {
      let decimal = 0
      for (let i = 0; i < columnName.length; i++) {
        const digit = columnName.charCodeAt(i) - 64
        decimal = decimal * 26 + digit
      }
      return decimal
    },
    // 10 进制转 26 进制。例如：27 -> AA
    convertToLetter(num) {
      const letters = []
      while (num > 0) {
        const remainder = (num - 1) % 26
        letters.unshift(String.fromCharCode(65 + remainder))
        num = Math.floor((num - remainder) / 26)
      }
      return letters.join('')
    },
    // 删除 dataProviderTableData 中的一行数据
    delTableRowDataProviderTableData(index) {
      this.isChange = true
      this.dataProviderTableData.splice(index, 1) // 删除一行
    },
    // 点击 history 按钮
    handleHistory() {
      this.historyDialogFormVisible = true
      this.$nextTick(() => {
        this.$refs.testCaseHistoryComponent.loadData(this.element.id)
      })
    },
    // 双击历史结果行
    dblclickResultRow(ele) {
      this.historyDialogFormVisible = false
      this.setElement(ele, ele.caseType)
    },
    // 点击发送
    handleSend() {
      this.$refs['element'].validate((valid) => {
        if (valid) {
          if (this.element.id === undefined) {
            this.$message({ message: '发送失败，请先保存测试用例后再点击发送按钮!', type: 'error' })
            return
          }

          this.isChange = false
          this.element.type = this.caseType // 分类 type，3 接口测试用例
          this.element.caseType = this.caseType // 用例 type: 1 手工用例 , 2 自动化用例

          // 删除空步骤
          const delIndexs = []
          this.element.steps.forEach((item, index) => {
            if (item.stepType === 12) { // WebSocket 接口，需要将 json 对象转成字符串保存到数据库
              if (typeof item.extra4 === 'object') {
                item.extra4 = JSON.stringify(item.extra4)
              }
            }
            if (item.apiId === 0 && item.desc === '') {
              delIndexs.push(index)
            }
          })
          for (let i = delIndexs.length - 1; i >= 0; i--) {
            this.element.steps.splice(delIndexs[i], 1)
          }

          // 删除 dataProvider 的中间空数据行
          this.dataProviderTableData = this.dataProviderTableData.filter(item => {
            let erasure = true
            for (const key in item) {
              if (item[key].length > 0) {
                erasure = false
                break
              }
            }
            return !erasure
          })
          this.dataProviderTableData.push({}) // 最后添加空行
          this.element.dataProvider = JSON.stringify(this.dataProviderTableData) // 设置 dataProvider
          const tempData = {
            projectId: this.$store.getters.projectId,
            env: this.debugEnv,
            agentId: this.debugAgentId,
            testCase: this.element
          }
          testCaseDebug(tempData).then((response) => {
            this.$message({ message: '请求发送成功，请在测试报告中查看执行结果!', type: 'success' })
          })
        }
      })
    },
    // 设置父 ID（遇到 if、for 这种有 childSteps 的需要用到 parentId）
    setParent(id) {
      this.parentId = id
    }
  }
}
</script>
